package ar.edu.caece.compiler.grammar.factories;

import ar.edu.caece.compiler.error.ErrorManager;
import ar.edu.caece.compiler.error.ParsingException;
import ar.edu.caece.compiler.grammar.AbstractTokenVisitorFactory;
import ar.edu.caece.compiler.grammar.Recognizer;
import ar.edu.caece.compiler.grammar.TokenRecognizer;
import ar.edu.caece.compiler.grammar.TokenVisitor;
import ar.edu.caece.compiler.grammar.attributes.Attribute;
import ar.edu.caece.compiler.grammar.attributes.AttributeSymbolTable;
import ar.edu.caece.compiler.grammar.attributes.AttributeUtils;
import ar.edu.caece.compiler.grammar.attributes.BooleanAttribute;
import ar.edu.caece.compiler.grammar.attributes.tree.NodeDeclaraciones;
import ar.edu.caece.compiler.grammar.attributes.tree.NodeFuncProc;
import ar.edu.caece.compiler.grammar.attributes.tree.NodeListaBloques;
import ar.edu.caece.compiler.grammar.nonterminals.BLOQUEI;
import ar.edu.caece.compiler.grammar.nonterminals.LISTABLOQUES;
import ar.edu.caece.compiler.grammar.nonterminals.PROGRAMA;
import ar.edu.caece.compiler.grammar.terminals.EofToken;
import ar.edu.caece.compiler.model.LexicalAnalyzer;

public class PROGRAMAFactory extends AbstractTokenVisitorFactory {

	private static PROGRAMAFactory INSTANCE;
	
	public static PROGRAMAFactory getInstance() throws Exception {
		if(INSTANCE==null) INSTANCE = new PROGRAMAFactory();
		return INSTANCE;
	}
	
	public static PROGRAMAFactory getInstance(LexicalAnalyzer analizadorLexico) throws Exception {
		if(INSTANCE==null) INSTANCE = new PROGRAMAFactory(analizadorLexico);
		return INSTANCE;
	}
	
	private BLOQUEIFactory bloqueiFactory;
	private LISTABLOQUESFactory listabloquesFactory;
	private TokenVisitor parseBLOQUEI;
	private TokenVisitor parseLISTABLOQUES;
	
	private TokenVisitor parseEof;
	
	private Initializer initializer = new Initializer() {
		
		@Override
		public void init() throws Exception {
			initializer = NoOperationInitializer.getInstance();
			bloqueiFactory = BLOQUEIFactory.getInstance();
			listabloquesFactory = LISTABLOQUESFactory.getInstance();
			parseLISTABLOQUES = listabloquesFactory.makeVisitor();
			parseBLOQUEI = bloqueiFactory.makeVisitor();
			
			parseEof = new EofToken.EofTokenVisitor() {

				@Override
				public Recognizer eofCase(EofToken llamador) {
					return llamador;
				}

				@Override
				public Recognizer defaultCase(TokenRecognizer llamador)
						throws Exception {
					ErrorManager.getInstance().manage(llamador,"EofToken",new ParsingException("Error de parseo. Se recibi�: " +llamador.getToken(),EofToken.class,llamador));
					return null;
				}
				
			};
		}
	};
	
	private PROGRAMAFactory() throws Exception {

	}
	
	private PROGRAMAFactory(LexicalAnalyzer analizadorLexico) {
		super(analizadorLexico);
	}

	@Override
	public TokenVisitor makeChainedVisitor(
			final TokenVisitor siguiente) throws Exception {
		initializer.init();

		return new TokenVisitor() {

			@Override
			public Recognizer defaultCase(TokenRecognizer llamador)
					throws Exception {
				NodeListaBloques programaPrincipal = new NodeListaBloques();

				TokenVisitor parseBLOQUEI2 = bloqueiFactory.makeChainedVisitor(siguiente);
				parseBLOQUEI2.addInheritedAttribute(Attribute.TABLA_SIMBOLOS_HEREDADA, new AttributeSymbolTable());
				//Necesita recibir eso, un bloque FuncPro
				parseBLOQUEI2.addInheritedAttribute(Attribute.ARBOL_HEREDADO,new NodeFuncProc());

				Recognizer result = llamador.execute(parseBLOQUEI2);
				if(result instanceof BLOQUEI) {
					
					BLOQUEI bloquei = (BLOQUEI) result;

					parseLISTABLOQUES.addInheritedAttribute(Attribute.TABLA_SIMBOLOS_HEREDADA, bloquei.getAttribute(Attribute.TABLA_SIMBOLOS_SINTETIZADA));
					parseLISTABLOQUES.addInheritedAttribute("ultimo", new BooleanAttribute(false));
					
					parseLISTABLOQUES.addInheritedAttribute(Attribute.ARBOL_HEREDADO, new NodeListaBloques());
					LISTABLOQUES listabloques = (LISTABLOQUES) nextToken().execute(parseLISTABLOQUES);
					
					if( ((BooleanAttribute) listabloques.getAttribute("ultimo")).getValue()) {
						//TODO ERROR
						AttributeUtils.error("Error. Procedimiento �ltimo",llamador);
					}
					EofToken eofToken = (EofToken) nextToken().execute(parseEof);
					
					PROGRAMA programa = new PROGRAMA (bloquei, listabloques, eofToken);
					programaPrincipal = (NodeListaBloques) listabloques.getAttribute(Attribute.ARBOL_SINTETIZADO);
					programaPrincipal.setGlobales((NodeDeclaraciones) bloquei.getAttribute(Attribute.ARBOL_SINTETIZADO));

					programaPrincipal.getPrincipal().propagarALlamadas();
					programa.addAttribute(Attribute.ARBOL_SINTETIZADO,programaPrincipal);
					return programa;
				} 
				else {
					return result;
				}
			}
			
		};
	
	}
	@Override
	public TokenVisitor makeVisitor() throws Exception {
		initializer.init();

		return new TokenVisitor() {

			@Override
			public Recognizer defaultCase(TokenRecognizer llamador) throws Exception {
				NodeListaBloques programaPrincipal = new NodeListaBloques();

				parseBLOQUEI.addInheritedAttribute(Attribute.TABLA_SIMBOLOS_HEREDADA, new AttributeSymbolTable());
				parseBLOQUEI.addInheritedAttribute(Attribute.ARBOL_HEREDADO,new NodeFuncProc());
				BLOQUEI bloquei = (BLOQUEI) llamador.execute(parseBLOQUEI);

				parseLISTABLOQUES.addInheritedAttribute(Attribute.TABLA_SIMBOLOS_HEREDADA, bloquei.getAttribute(Attribute.TABLA_SIMBOLOS_SINTETIZADA));
				parseLISTABLOQUES.addInheritedAttribute("ultimo", new BooleanAttribute(false));
				
				parseLISTABLOQUES.addInheritedAttribute(Attribute.ARBOL_HEREDADO, new NodeListaBloques());
				
				LISTABLOQUES listabloques = (LISTABLOQUES) nextToken().execute(parseLISTABLOQUES);
				
				/*
				if(((BooleanAttribute) listabloques.getAttribute("ultimo")).getValue()) {
					//TODO ERROR
					AttributeUtils.error("Error",llamador);
				}
				*/
				EofToken eofToken = (EofToken) nextToken().execute(parseEof);
				
				PROGRAMA programa = new PROGRAMA (bloquei, listabloques, eofToken);
				
				programaPrincipal = (NodeListaBloques) listabloques.getAttribute(Attribute.ARBOL_SINTETIZADO);
				programaPrincipal.setGlobales((NodeDeclaraciones) bloquei.getAttribute(Attribute.ARBOL_SINTETIZADO));

				programaPrincipal.getPrincipal().propagarALlamadas();
				programa.addAttribute(Attribute.ARBOL_SINTETIZADO,programaPrincipal);
				return programa;
				
			}
		};
	}
	
	
}
